---
    title: '高级材质'
---

THREE.MeshPhongMaterial和THREE.MeshLambertMaterial。这两种材质会对光源做出反应，并且可以分别用来创建光亮的材质和暗淡的材质。本节还会介绍一种最通用但也最难用的材质：THREE.ShaderMaterial。通过THREE.ShaderMaterial，可以创建自己的着色程序，定义材质和物体如何显示。

## THREE.MeshLambertMaterial

这种材质可以用来创建暗淡的并不光亮的表面。该材质非常易用，而且会对场景中的光源产生反应。

import { Scene } from './03a-mesh-lambert-material.jsx';

<Scene />

<br/>

创建MeshLambertMaterial很简单. 参数除了基本属性还有:

|名称|说明|
|---|---|
|color|材料的环境色|
|emissive|材质自发光的颜色.该属性虽然不会让使用它的物体变成光源,但是会使物体的颜色不受其他光源的影响.在没有光源的的暗处,设置该值,可以实现物体自发光|
|wireframe|该属性可以true时,可以绘制具有光照效果的线框物体.|


```jsx title='chapter-04/03a-mesh-lambert-material.jsx'
var meshMaterial = new THREE.MeshLambertMaterial({color: 0x7777ff});
```


## THREE.MeshPhongMaterial

通过THREE.MeshPhongMaterial可以创建一种光亮的材质.


import { Scene as SceneB } from './03b-mesh-phong-material.jsx';

<SceneB />

<br/>

创建MeshPhongMaterial很简单. 参数除了基本属性还有:

|名称|说明|
|---|---|
|color|材料的环境色|
|emissive|材质自发光的颜色.该属性虽然不会让使用它的物体变成光源,但是会使物体的颜色不受其他光源的影响.在没有光源的的暗处,设置该值,可以实现物体自发光|
|specular|该属性指定该材质的光亮程度及高光部分的颜色,如果将它设置和color属性相同的颜色,将会得到一个更加类似金属的材质.如果将他设置成灰色(grey),材质将变得更像塑料|
|shiness|该属性指定物体表面镜面高光部分的轮廓的清晰度,越光滑的表面,高光部分越清晰,反之越模糊,该值默认30|

```jsx title='chapter-04/03b-mesh-phong-material.jsx'
var meshMaterial = new THREE.MeshPhongMaterial({color: 0x7777ff});
```


## THREE.MeshStardardMaterial

THREE.MeshStandardMaterial这种材质使用更加正确的物理计算来决定物体表面如何与场景中的光源互动。这种材质能够更好地表现塑料质感和金属质感的表面.

import { Scene as SceneC } from './03c-mesh-standard-material.jsx';

<SceneC />

<br/>

创建MeshStardardMaterial很简单. 参数除了基本属性还有:

|名称|说明|
|---|---|
|metalness(金属感程度)|该属性控制物体表面的金属质感程度.0代表完全塑料质感,1代表完全金属质感|
|roughness(粗糙程度)|该属性控制物体表面的粗糙程度.在视觉上,它决定表面对入射光的漫反射程度.当值为0时会产生类似镜面反射的效果,当1时会产生完全的漫反射效果.|

## THREE.MeshPhysicaMaterial

该材质与THREE.MeshStandardMaterial非常相似，但提供了对反光度的更多控制，因此它除了具有标准材质的全部属性之外:

|名称|说明|
|---|---|
|clearCoat(清漆)|该属性控制物体表面清漆涂层效果的明显程度,该属性值越高,则清漆涂层越厚,其结果是clearCoatRoughness属性带来的影响越明显|
|clearCoatRoughness(清漆粗糙程度)|该属性控制物体表面清漆涂层的粗糙程度,粗糙程度越高,漫反射越明显.该属性需要和上面clearCoat属性配合使用|
|reflectivity|该属性用于控制非金属表面的反光度,因此当metalness(金属感程度)接近1时该属性的作用很小.|

import { Scene as SceneD } from './03d-mesh-physica-material.jsx';

<SceneD />

<br/>


:::info
与其他材质类似，这两种新材质在不动手实验的情况下，也难以确定什么样的参数值才能最符合某种特定需求。因此最佳的实践方法就是给程序增加一个简单的UI（就像我们在前面示例中所做的那样），并通过一边调节参数一边观察的方法来寻找最满意的参数组合。
:::


## THREE.ShaderMaterial

THREE.ShaderMaterial是Three.js库中最通用、最复杂的材质之一。通过它，可以使用自己定制的着色器，直接在WebGL环境中运行。
着色器可以将Three.js中的JavaScript网格转换为屏幕上的像素。
通过这些自定义的着色器，可以明确地指定对象如何渲染，以及如何覆盖或修改Three.js库中的默认值。


THREE.ShaderMaterial 有一些我们已经见过的属性可以设置属性.如wireframe,wireframeLinewidth,linewidth,shading,vertexColors,fog.
除了基础属性之外还有几个自己本身的属性,可以用来给自己定制着色器传入更多信息.

|名称|描述|
|---|---|
|fragmentShader|这个着色器定义的是每个传入像素的颜色.你需要传入像素着色器程序的字符串|
|vertexShader|这个着色器允许修改每一个传入的顶点位置,你需要传入顶点着色器程序的字符串|
|uniforms|通过这个属性可以向你的着色器发信息,同样的信息会发送给每一个顶点和片段|
|defines|转化成#define代码片段,这些代码片段可以用来设置着色器程序里面的一些额外的全局变量|
|lights|该属性定义光照数据是否传递给着色器.|

要创建一个ShaderMaterial必须传入两个不同的着色器:
* vertexShader: 它会在几何体的每一个顶点上执行。可以用这个着色器通过改变顶点的位置来对几何体进行变换。
* fragmentShader: 它会在几何体的每一个片段上执行。在VertexShader里，我们会返回这个特定片段应该显示的颜色。

这个例子使用了一个非常简单的vertexShader程序，用来修改一个方块各个顶点的x、y、z坐标，还会用到一个fragmentShader程序，使用来自http://glslsandbox.com/的着色器创建连续变化的材质。

接下来你会看到我们使用vertexShader的完整代码。请注意，着色器不是用JavaScript编写的。
需要使用类似C的GLSL语言（WebGL支持OpenGL ES着色语言1.0——更多关于GLSL的信息，参考 [https://www.khronos.org/webgl/](https://www.khronos.org/webgl/) ）来写着色器.

```glsl title='chapter-04/03e-shader-material.jsx'
uniform float time;
varying vec2 vUv;

void main()
{
    vec3 posChanged = position;
    posChanged.x = posChanged.x*(abs(sin(time*1.0)));
    posChanged.y = posChanged.y*(abs(cos(time*1.0)));
    posChanged.z = posChanged.z*(abs(sin(time*1.0)));
    //gl_Position = projectionMatrix * modelViewMatrix * vec4(position*(abs(sin(time)/2.0)+0.5),1.0);
    gl_Position = projectionMatrix * modelViewMatrix * vec4(posChanged,1.0);
}
```

要与JavaScript中的着色器通信，我们会使用一种称为“统一值”（uniform）的方法。
在这个例子中，我们使用“uniform float time；”语句传入一个外部值。
基于此值，我们会改变传入顶点的x、y、z坐标（通过position变量传入）

```
vec3 posChanged = position;
posChanged.x = posChanged.x*(abs(sin(time*1.0)));
posChanged.y = posChanged.y*(abs(cos(time*1.0)));
posChanged.z = posChanged.z*(abs(sin(time*1.0)));
```

现在向量posChanged包含的就是顶点的新坐标，根据传入的time变量计算得到。我们最后一步要做的就是将这个新坐标传回Three.js.
gl_Position变量是一个特殊变量，用来返回最终的位置.


import { Scene as SceneE } from './03e-shader-material.jsx';

<SceneE />

<br/>

在这个例子里，方块的每个面都在不断变化。正是每个面上的fragment Shader造就了这种变化。
在本例中，所有的fragmentShader对象都是从http://glslsandbox.com/复制过来的。
这个网站提供了一个试验环境，可以在这里创建和分享fragmentShader对象。

```
  precision highp float;
    uniform float time;
    uniform float alpha;
    uniform vec2 resolution;
    varying vec2 vUv;

    void main2(void)
    {
    vec2 position = vUv;
    float red = 1.0;
    float green = 0.25 + sin(time) * 0.25;
    float blue = 0.0;
    vec3 rgb = vec3(red, green, blue);
    vec4 color = vec4(rgb, alpha);
    gl_FragColor = color;
    }

    #define PI 3.14159
    #define TWO_PI (PI*2.0)
    #define N 68.5

    void main(void)
    {
    vec2 center = (gl_FragCoord.xy);
    center.x=-10.12*sin(time/200.0);
    center.y=-10.12*cos(time/200.0);

    vec2 v = (gl_FragCoord.xy - resolution/20.0) / min(resolution.y,resolution.x) * 15.0;
    v.x=v.x-10.0;
    v.y=v.y-200.0;
    float col = 0.0;

    for(float i = 0.0; i < N; i++)
    {
    float a = i * (TWO_PI/N) * 61.95;
    col += cos(TWO_PI*(v.y * cos(a) + v.x * sin(a) + sin(time*0.004)*100.0 ));
    }

    col /= 5.0;

    gl_FragColor = vec4(col*1.0, -col*1.0,-col*4.0, 1.0);
}
```

最终传回给Three.js的颜色是通过语句gl_FragColor=color_final设置的。


